﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {

    /// <summary>
    /// オフラインレンダリングのためのフレームバッファークラス
    /// </summary>
    /// <remarks>
    /// レンダーターゲットには必ずテクスチャー(<see cref="Sampler"/>)を使用します。
    /// OpenGLのレンダーバッファーに相当するものは存在しません。
    /// </remarks>
    public class FrameBuffer : Object {
        #region Field
        Image2D[] colorBuffers;
        Image2D[] depthBuffers;
        Image2D[] stencilBuffers;
        bool[] colorClears;
        bool[] depthClears;
        bool[] stencilClears;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 現在ステンシルバッファーは対応していません
        /// </remarks>
        /// <param name="numColorBuffers">カラーバッファーの総数(0以上)</param>
        /// <param name="numDepthBuffer">デプスバッファーの総数(0または1)</param>
        /// <param name="numStencilBuffer">ステンシルバッファーの総数(0または1)</param>
        public FrameBuffer (int numColorBuffers, int numDepthBuffer, int numStencilBuffer) {
            if (numColorBuffers < 0) {
                throw new ArgumentException ("Number of color buffer is invalid");
            }
            if (numDepthBuffer < 0 || numDepthBuffer > 1) {
                throw new ArgumentException ("Number of depth buffer is invalid");
            }
            if (numStencilBuffer < 0 || numStencilBuffer > 1) {
                throw new ArgumentException ("Number of stencil buffer is invalid");
            }

            this.colorBuffers = new Image2D[numColorBuffers];
            this.depthBuffers = new Image2D[numDepthBuffer];
            this.stencilBuffers = new Image2D[numStencilBuffer];

            this.colorClears = new bool[numColorBuffers];
            this.depthClears = new bool[numDepthBuffer];
            this.stencilClears = new bool[numStencilBuffer];
            for (var i = 0; i < colorClears.Length; i++) {
                this.colorClears[i] = true;
            }
            for (var i = 0; i < depthClears.Length; i++) {
                this.depthClears[i] = true;
            }
            for (var i = 0; i < stencilClears.Length; i++) {
                this.stencilClears[i] = true;
            }
        }
        #endregion

        #region Property
        /// <summary>
        /// カラーバッファーの総数
        /// </summary>
        public int ColorBufferCount {
            get { return colorBuffers.Length; }
        }

        /// <summary>
        /// デプスバッファーの総数
        /// </summary>
        public int DepthBufferCount {
            get { return depthBuffers.Length; }
        }

        /// <summary>
        /// ステンシルバッファーの総数
        /// </summary>
        public int StencilBufferCount {
            get { return stencilBuffers.Length; }
        }


        public bool DepthClear {
            get {
                if (DepthBufferCount == 0) {
                    throw new InvalidOperationException ("No depth buffer");
                }
                return depthClears[0];
            }
        }

        public bool StencilClear {
            get {
                if (StencilBufferCount == 0) {
                    throw new InvalidOperationException ("No stencil buffer");
                }
                return stencilClears[0];
            }
        }

        /// <summary>
        /// カラーバッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 列挙子中にnullは含まれません
        /// </remarks>
        public IEnumerable<Image2D> ColorBuffers {
            get { return colorBuffers.Where (x => x != null); }
        }

        /// <summary>
        /// デプスバッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 列挙子中にnullは含まれません
        /// </remarks>
        public IEnumerable<Image2D> DepthBuffers {
            get { return depthBuffers.Where(x=>x!=null); }
        }

        /// <summary>
        /// ステンシルバッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 列挙子中にnullは含まれません
        /// </remarks>
        public IEnumerable<Image2D> StencilBuffers {
            get { return stencilBuffers.Where (x => x != null); }
        }

        /// <summary>
        /// 0番目のカラーバッファー
        /// </summary>
        public Image2D ColorBuffer {
            get { return colorBuffers.ElementAtOrDefault (0); }
        }

        public int GetColorBufferIndex (Image2D cbuf) {
            if (cbuf == null) {
                throw new ArgumentNullException ("cbuf is null");
            }
            if (!colorBuffers.Contains (cbuf)) {
                throw new ArgumentException ("cbuf is not in the ColorBuffers");
            }
            return Array.IndexOf(colorBuffers, cbuf);
        }

        /// <summary>
        /// 0番目のデプスバッファー
        /// </summary>
        public Image2D DepthBuffer {
            get { return depthBuffers.ElementAtOrDefault(0); }
        }

        /// <summary>
        /// 0番目のステンシルバッファー
        /// </summary>
        public Image2D StencilBuffer {
            get { return stencilBuffers.ElementAt(0); }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (colorBuffers);
                objs.AddRange (depthBuffers);
                objs.AddRange (stencilBuffers);
                objs.RemoveAll (x=>x==null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// i番目のカラーバッファーを取得します
        /// </summary>
        /// <param name="index">カラーバッファー番号</param>
        /// <returns>テクスチャー</returns>
        public Image2D GetColorBuffer (int index) {
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Indx is out of range");
            }
            return colorBuffers[index];
        }

        /// <summary>
        /// デプスバッファーを取得します
        /// </summary>
        /// <returns>テクスチャー</returns>
        public Image2D GetDepthBuffer () {
            if (DepthBufferCount <= 0) {
                throw new IndexOutOfRangeException ("No depth buffer");
            }
            return depthBuffers[0];
        }

        /// <summary>
        /// ステンシルバッファーを取得します
        /// </summary>
        /// <returns>テクスチャー</returns>
        public Image2D GetStencilBuffer () {
            throw new NotImplementedException ("Sorry");
            if (StencilBufferCount <= 0) {
                throw new IndexOutOfRangeException ("No stencil buffer");
            }
                        return stencilBuffers[0];
        }


        /// <summary>
        /// デプスバッファーをセットします
        /// </summary>
        /// <param name="tex">テクスチャー</param>
        public void SetDepthBuffer (Image2D tex) {
            if (DepthBufferCount == 0) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.depthBuffers[0] = tex;
        }

        /// <summary>
        /// 0番目のステンシルバッファー
        /// </summary>
        /// <remarks>
        /// 現在未実装です
        /// </remarks>
        /// <param name="tex">テクスチャー</param>
        public void SetStencilBuffer (Image2D tex) {
            throw new NotImplementedException ("Sorry");
            //this.stencilBuffers[0] = tex;
        }


        /// <summary>
        /// i番目のカラーバッファーをセットします
        /// </summary>
        /// <param name="index">カラーバッファー番号</param>
        /// <param name="tex">テクスチャー</param>
        public void SetColorBuffer (int index, Image2D tex) {
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.colorBuffers[index] = tex;
        }


        public bool GetColorClear (int index) {
            if (ColorBufferCount == 0) {
                throw new InvalidOperationException ("No color buffer");
            }
            if (index < 0 || index >= ColorBufferCount) {
                throw new IndexOutOfRangeException ("Index is invalid");
            }
            return colorClears[index];
        }

        public bool GetDepthClear () {
            if (DepthBufferCount == 0) {
                throw new InvalidOperationException ("No depth buffer");
            }
            return depthClears[0];
        }

        public bool GetStencilClear () {
            if (StencilBufferCount == 0) {
                throw new InvalidOperationException ("No stencil buffer");
            }
            return stencilClears[0];
        }

        public void SetClear (bool colorClear, bool depthClear, bool stencilClear) {
            for (var i = 0; i < ColorBufferCount; i++) {
                this.colorClears[i] = colorClear;
            }
            for (var i = 0; i < DepthBufferCount; i++) {
                this.depthClears[i] = depthClear;
            }
            for (var i = 0; i < StencilBufferCount; i++) {
                this.stencilClears[i] = stencilClear;
            }
        }

        public void SetClear (bool[] colorClears, bool depthClear, bool stencilClear) {
            if (colorClears == null) {
                throw new ArgumentNullException ("ColorClears is null");
            }
            if (colorClears.Length < ColorBufferCount) {
                throw new ArgumentException ("ColorClears is too short");
            }

            for (var i = 0; i < ColorBufferCount; i++) {
                this.colorClears[i] = colorClears[i];
            }
            for (var i = 0; i < DepthBufferCount; i++) {
                this.depthClears[i] = depthClear;
            }
            for (var i = 0; i < StencilBufferCount; i++) {
                this.stencilClears[i] = stencilClear;
            }
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("FrameBuffer: {0}, {1}, {2}", ColorBufferCount, DepthBufferCount, StencilBufferCount);
        }
        #endregion
    }
}
